package blazingSaddles;

import game.GameMove;
import game.GamePlayer;
import game.GameState;
import game.Util;
import breakthrough.*;

import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;



public class BlazingBreakthroughPlayer extends GamePlayer {
	
	public enum Difficulty {
		EASY, MEDIUM, HARD
	}

	int depthLimit;
	final int MAX_DEPTH = 100;
	final static double MAX_SCORE = 100000.0;
	ArrayList<String> quotes;
	
	
	//evaluation weights, using integers just for now
	final static int W_ALIVE = 500; // piece being alive - EASY
	final static int W_DANGER = 20; // piece can be attacked and is not protected - HARD
	final static int W_WIN_PROXIMITY = 10; //multiplied by row to increase value of proximity to enemy base -EASY
	final static int M_WIN_PROXIMITY_2 =10; // 2 moves from victory, not blocked/defended, additional multiplier to proximity -Hard
	final static int M_WIN_PROXIMITY_1 = 50; // 1 move from victory, not blocked/defended, additional multiplier to proximity -Medium
	final static int W_PROTECTED = 35; // piece being protected by another piece -Easy
	final static int W_COL_HOLE = 20; // a hole in a player's line -Easy
	final static int W_HORZ_ADJ = 20; // pair of pieces next to each other (only counted once) -Medium
	final static int W_VERT_ADJ = 10; // pieces behind a friendly (only counted once) -Medium
	final static int W_MOVE_FREEDOM = 1; // Per space free
	final static int W_HOMEROW_DEFENSE = 10; // Easy
	
	private static Difficulty diff;
	
	
	ScoredBreakthroughMove[] mvStack;
	protected class ScoredBreakthroughMove extends BreakthroughMove{
		public ScoredBreakthroughMove(int r1, int c1, int r2, int c2, double s){
			super(r1, c1, r2, c2);
			this.score = s;
		}
		public void set(int r1, int c1,int r2,int c2, double s){
			super.startRow=r1;
			super.startCol=c1;
			super.endingCol=c2;
			super.endingRow=r2;
			score = s;
		}
		public void setMove(BreakthroughMove mv){
			super.startRow = mv.startRow;
			super.startCol = mv.startCol;
			super.endingCol = mv.endingCol;
			super.endingRow = mv.endingRow;
		}
		public double score;
	}
	private void readQuotes(){
		quotes = new ArrayList<String>();
		try {
			Scanner input = new Scanner(new File("blazingSaddles\\Quotes.txt"));
			String line;
			while (input.hasNext()) {
				line = input.nextLine();
				quotes.add(line);
			}
			input.close();
		}
		catch (Exception e) {
			System.err.printf("Problem reading %s%n", "Quotes.txt");
			e.printStackTrace();
		}
	}
	public String messageForOpponent(String opponent){
		return quotes.get((int)(Math.random()*quotes.size()-1));
	}
	
	/*
	 * Called at the state of the game
	 * @see game.GamePlayer#init()
	 */
	public void init()
	{	
		readQuotes();
		mvStack = new ScoredBreakthroughMove[MAX_DEPTH];
		for(int i=0;i<MAX_DEPTH;i++){
			mvStack[i] = new ScoredBreakthroughMove(0,0,0,0,0.0);
		}
	}
	public BlazingBreakthroughPlayer(String nickname, int depthLimit) {
		super(nickname, new BreakthroughState(), true);
		this.depthLimit = depthLimit;
		BlazingBreakthroughPlayer.diff = BlazingBreakthroughPlayer.Difficulty.HARD;
		
		// TODO Auto-generated constructor stub
	}

	/*
	 * (non-Javadoc)
	 * @see game.GamePlayer#getMove(game.GameState, java.lang.String)
	 */
	public GameMove getMove(GameState state, String lastMv) {	
		alphaBeta((BreakthroughState)state, 0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
		System.out.println("Score =" + mvStack[0].score);
		return mvStack[0];

	}


	public BreakthroughMove[] possible(GameState state, GameState.Who turn){
		BreakthroughState brd = (BreakthroughState)state;
		char player = turn == GameState.Who.HOME ? BreakthroughState.homeSym : BreakthroughState.awaySym;

		ArrayList<BreakthroughMove> list = new ArrayList<BreakthroughMove>();
		for(int i =0;i<BreakthroughState.N;i++){
			for(int j=0;j<BreakthroughState.N;j++){
				if(brd.board[i][j]== player){
					if(player == BreakthroughState.homeSym){
						if(j!=0){
							if(j!=BreakthroughState.N-1){
								BreakthroughMove diagLeft = new BreakthroughMove(i,j,i+1,j-1);
								BreakthroughMove up = new BreakthroughMove(i,j,i+1,j);
								BreakthroughMove diagRight = new BreakthroughMove(i,j,i+1,j+1);
								if(brd.moveOK(diagLeft)){
									list.add(diagLeft);
								}
								if(brd.moveOK(diagRight)){
									list.add(diagRight);
								}
								if(brd.moveOK(up)){
									list.add(up);
								}
							}
							else{
								BreakthroughMove diagLeft = new BreakthroughMove(i,j,i+1,j-1);
								BreakthroughMove up = new BreakthroughMove(i,j,i+1,j);
								if(brd.moveOK(diagLeft)){
									list.add(diagLeft);
								}
								if(brd.moveOK(up)){
									list.add(up);
								}
							}
						}
						else{
							BreakthroughMove up = new BreakthroughMove(i,j,i+1,j);
							BreakthroughMove diagRight = new BreakthroughMove(i,j,i+1,j+1);
							if(brd.moveOK(diagRight)){
								list.add(diagRight);
							}
							if(brd.moveOK(up)){
								list.add(up);
							}
						}
					}
					else{ //Away Player
						if(j!=0){
							if(j!=BreakthroughState.N-1){
								BreakthroughMove diagLeft = new BreakthroughMove(i,j,i-1,j-1);
								BreakthroughMove up = new BreakthroughMove(i,j,i-1,j);
								BreakthroughMove diagRight = new BreakthroughMove(i,j,i-1,j+1);
								if(brd.moveOK(diagLeft)){
									list.add(diagLeft);
								}
								if(brd.moveOK(diagRight)){
									list.add(diagRight);
								}
								if(brd.moveOK(up)){
									list.add(up);
								}
							}
							else{
								BreakthroughMove diagLeft = new BreakthroughMove(i,j,i-1,j-1);
								BreakthroughMove down = new BreakthroughMove(i,j,i-1,j);
								if(brd.moveOK(diagLeft)){
									list.add(diagLeft);
								}
								if(brd.moveOK(down)){
									list.add(down);
								}
							}
						}
						else{
							BreakthroughMove down = new BreakthroughMove(i,j,i-1,j);
							BreakthroughMove diagRight = new BreakthroughMove(i,j,i-1,j+1);
							if(brd.moveOK(diagRight)){
								list.add(diagRight);
							}
							if(brd.moveOK(down)){
								list.add(down);
							}
						}
					}
				}
			}

		}
		BreakthroughMove[] tempArr = new BreakthroughMove[10];
		return list.toArray(tempArr);
	}

	public boolean detTerminal(BreakthroughState state, ScoredBreakthroughMove mv){
		GameState.Status status = state.getStatus();
		boolean isTerminal = true;

		if (status == GameState.Status.HOME_WIN) {
			mv.set(0,0,0,0, MAX_SCORE);
		} else if (status == GameState.Status.AWAY_WIN) {
			mv.set(0,0,0,0, - MAX_SCORE);
		} else if (status == GameState.Status.DRAW) {
			mv.set(0,0,0,0,0);
		} else {
			isTerminal = false;
		}
		return isTerminal;


	}

	private void alphaBeta(BreakthroughState state, int currDepth, double alpha, double beta){
		boolean ptoMax = (state.getWho() == GameState.Who.HOME);
		boolean ptoMin = !ptoMax;

		//Need to do
		boolean isTerminal = detTerminal(state,mvStack[currDepth]);

		if(isTerminal){
			;
		}
		else if(currDepth == depthLimit){
			double brdScore = evalBoard(state);
			//System.out.println(brdScore);
			mvStack[currDepth].set(0,0,0,0, brdScore);
		}
		else{
			ScoredBreakthroughMove temp = new ScoredBreakthroughMove(0,0,0,0,0);

			double bestScore = (ptoMax ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);

			ScoredBreakthroughMove bestMove = mvStack[currDepth];
			ScoredBreakthroughMove nextMove = mvStack[currDepth+1];

			bestMove.set(0,0,0,0,bestScore);
			GameState.Who currTurn = state.getWho();

			BreakthroughMove[] moves = possible(state, currTurn);
			shuffle(moves);
			//if(moves.length !=0){
			//	depthLimit = (int)25/(moves.length) + 6;
			//}
			
			for(int i =0;i<moves.length;i++){
				if(state.moveOK(moves[i])){
					temp.setMove(moves[i]);
					char prev = state.board[temp.endingRow][temp.endingCol];
					state.makeMove(temp);

					alphaBeta(state,currDepth+1,alpha,beta);


					//Undo move
					state.board[temp.startRow][temp.startCol] = currTurn == GameState.Who.HOME ?
							BreakthroughState.homeSym : BreakthroughState.awaySym;
					state.board[temp.endingRow][temp.endingCol] = prev;
					state.numMoves--;
					state.status = GameState.Status.GAME_ON;
					state.who = currTurn;


					//
					if(ptoMax && nextMove.score > bestMove.score){
						bestMove.setMove(temp);
						bestMove.score = nextMove.score;
					} else if(!ptoMax && nextMove.score < bestMove.score){
						bestMove.setMove(temp);
						bestMove.score = nextMove.score;
					}

					//Update Alpha and Beta
					if(ptoMin){
						beta = Math.min(bestMove.score, beta);
						if(bestMove.score <=alpha || bestMove.score == -MAX_SCORE){
							return;
						}
					}
					else {
						alpha = Math.max(bestMove.score, alpha);
						if(bestMove.score >= beta || bestMove.score == MAX_SCORE){
							return;
						}
					}
				}
			}


		}
	}
	protected static void shuffle(BreakthroughMove [] ary)
	{
		int len = ary.length;
		for (int i=0; i<len; i++) {
			int spot = Util.randInt(i, len-1);
			BreakthroughMove tmp = ary[i];
			ary[i] = ary[spot];
			ary[spot] = tmp;
		}
	}

	/**
	 * Evaluates the value of a piece in this location
	 * @param brd
	 * @param row
	 * @param col
	 * @return
	 */
	public static int evalPiece(BreakthroughState brd, int row, int col){
		int pieceScore = W_ALIVE; //Bonus for being alive
		char who = brd.board[row][col];
		
		if((diff == BlazingBreakthroughPlayer.Difficulty.MEDIUM || diff == BlazingBreakthroughPlayer.Difficulty.HARD) && getVertAdj(brd, row, col))
			pieceScore += W_VERT_ADJ;
		
		if((diff == BlazingBreakthroughPlayer.Difficulty.MEDIUM || diff == BlazingBreakthroughPlayer.Difficulty.HARD) && getHorzAdj(brd, row, col));
			pieceScore += W_HORZ_ADJ;
		
		boolean isProtected = false;
		if(isProtected = isProtectedLeft(brd, row, col))
			pieceScore += W_PROTECTED;
		
		if(isProtected = isProtectedRight(brd, row, col))
			pieceScore += W_PROTECTED;
		
		boolean isAttacked = isAttacked(brd, row, col);

		// Check for piece being in danger
		if(isAttacked && !isProtected && diff == BlazingBreakthroughPlayer.Difficulty.HARD)
			pieceScore += W_DANGER;

		// Check for victory proximity
		int proxScore = 0;
		boolean getsBonus2 = getBonus2(brd, row, col, isProtected, isAttacked);
		boolean getsBonus1 = getBonus1(brd, row, col, isProtected, isAttacked);
		
		if(who == BreakthroughState.homeSym){ // White
			if(row == 0)
				proxScore = W_HOMEROW_DEFENSE; // Small bonus for defending base
			else
				proxScore = W_WIN_PROXIMITY * row;
	
			
		}
		else{ // Black
				if(row == BreakthroughState.N-1)
					proxScore = W_HOMEROW_DEFENSE;
				else
					proxScore = W_WIN_PROXIMITY * (BreakthroughState.N-1-row);
		}
		
		if((diff == BlazingBreakthroughPlayer.Difficulty.MEDIUM || diff == BlazingBreakthroughPlayer.Difficulty.HARD) && getsBonus2){
			proxScore *= M_WIN_PROXIMITY_2;
		}
		else if((diff == BlazingBreakthroughPlayer.Difficulty.MEDIUM || diff == BlazingBreakthroughPlayer.Difficulty.HARD) && getsBonus1 )
			proxScore *= M_WIN_PROXIMITY_1;
		
		pieceScore += proxScore;
		//pieceScore *= (who == BreakthroughState.homeSym ? 1 : -1);
			
		return pieceScore;
	}

	public static double evalBoard(BreakthroughState brd){

		int score = 0;
		int numHomePieceCol; // used to keep track of white pieces in a column
		int numAwayPieceCol;

		// Examine the board, square by square
		for(int col = 0; col < BreakthroughState.N-1; col++){
			numHomePieceCol = 0;
			numAwayPieceCol = 0;
			for(int row = 0; row < BreakthroughState.N-1; row++){
				if(brd.board[row][col] != BreakthroughState.emptySym){
					if(brd.board[row][col] == BreakthroughState.homeSym){
						numHomePieceCol++;
						score += evalPiece(brd, row, col);
					}
					else{
						numAwayPieceCol++;
						score -= evalPiece(brd, row, col);
					}
				}
			}
			if(numHomePieceCol == 0){
				score -= W_COL_HOLE; // Advantage to Black, subtract

			}
			if(numAwayPieceCol == 0){
				score += W_COL_HOLE; // Advantage to White, add
			}
		}
		
		return (double)score;
	}

	public static boolean getVertAdj(BreakthroughState brd, int row, int col){
		char who = brd.board[row][col];

		//Detect vertical adjacency, this will be done by looking at the piece forward of it
		//Assume index bounds are safe as game would end if piece went on enemy home row
		if(row != 0 && who == BreakthroughState.homeSym && brd.board[row-1][col] == BreakthroughState.homeSym) // if white, look at piece in row+1
			return true;
		else if(row != BreakthroughState.N-1 && who == BreakthroughState.awaySym && brd.board[row+1][col] == BreakthroughState.awaySym) // if black, look at piece in row-1
			return true;
		
		return false;
	}
	
	public static boolean getHorzAdj(BreakthroughState brd, int row, int col){
		char who = brd.board[row][col];
		
		// Detect horizontal adjacency, we're looking for pairs, so scan piece next to it in one direction
		// so that this isn't counted twice.
		if(col != 0 && who == BreakthroughState.homeSym && brd.board[row][col-1] == BreakthroughState.homeSym)
			return true;
		else if(col != BreakthroughState.N-1 && who == BreakthroughState.awaySym && brd.board[row][col+1] == BreakthroughState.awaySym)
			return true;
		
		return false;
	}
	
	public static boolean isProtectedLeft(BreakthroughState brd, int row, int col){
		// Detect protection from rear diagonal friendlies
		char who = brd.board[row][col];
		boolean isProtected = false; // can be used to adjust penalty of being attacked unprotected
		
		if(col == 0)
			return false;
		else if(who == BreakthroughState.homeSym && row != 0
				&& brd.board[row-1][col-1] == BreakthroughState.homeSym){
				isProtected = true;
		}
			//if(){
			//	isProtected = true;
			//}
		else if(who == BreakthroughState.awaySym && row != BreakthroughState.N-1
				&& brd.board[row+1][col-1] == BreakthroughState.awaySym){ // black
	
				isProtected = true;
		}
			//if(col != BreakthroughState.N-1 && brd.board[row+1][col+1] == BreakthroughState.awaySym){
			//	isProtected = true;
			//}
		return isProtected;
	}
	
	public static boolean isProtectedRight(BreakthroughState brd, int row, int col){
		// Detect protection from rear diagonal friendlies
		char who = brd.board[row][col];
		
		if(col == BreakthroughState.N-1)
			return false;
		else if(who == BreakthroughState.homeSym && row != 0
				&& brd.board[row-1][col+1] == BreakthroughState.homeSym){
			return true;
		}
		else if(who == BreakthroughState.awaySym && row != BreakthroughState.N-1
				&& brd.board[row+1][col+1] == BreakthroughState.awaySym){
			return true;
		}
		return false;
	}
	
	public static boolean isAttacked(BreakthroughState brd, int row, int col){
		// Detect if piece is being attacked
		boolean isAttacked = false;
		char who = brd.board[row][col];
		if(who == BreakthroughState.homeSym){
			if((col != 0 && brd.board[row+1][col-1] == BreakthroughState.awaySym) 
					|| (col != BreakthroughState.N-1 && brd.board[row+1][col+1] == BreakthroughState.awaySym)){
				isAttacked = true;
			}
		}
		else{ // Away
			if((col != 0 && brd.board[row-1][col-1] == BreakthroughState.homeSym) 
					|| (col!= BreakthroughState.N-1 && brd.board[row-1][col+1] == BreakthroughState.homeSym))
				isAttacked = true;
		}
		return isAttacked;
	}
	
	public static boolean getBonus2(BreakthroughState brd, int row, int col, boolean isProtected, boolean isAttacked){
		char who = brd.board[row][col];
		if(who == BreakthroughState.homeSym
				&& row == BreakthroughState.N-3 && (isProtected || !isAttacked))//((isAttacked && isProtected) || !isAttacked))
		{
			
				return true;
			//else if(row == BreakthroughState.N-2 && ((isAttacked && isProtected) || !isAttacked)){ // 1 move away
			//	proxScore *= M_WIN_PROXIMITY_1;
			//}
		}
		else if(who == BreakthroughState.awaySym
				&& row == 2 && (isProtected || !isAttacked))//((isAttacked && isProtected) || !isAttacked))
		{
			return true;
		}
		return false;
	}
	
	public static boolean getBonus1(BreakthroughState brd, int row, int col, boolean isProtected, boolean isAttacked){
		char who = brd.board[row][col];
		if(who == BreakthroughState.homeSym
				&& row == BreakthroughState.N-2 && (isProtected || !isAttacked))//((isAttacked && isProtected) || !isAttacked))
				{
				return true;
		}
		else if(who == BreakthroughState.awaySym
				&& row == 1 && (isProtected || !isAttacked))//((isAttacked && isProtected) || !isAttacked))
				{
			return true;
		}
		return false;
	}

	
	
	public static void main(String [] args)
	{
		GamePlayer p = new BlazingBreakthroughPlayer("Blazing+", 6);
		p.compete(args);
	}
}
